Swift Share

前段时间在公司内部做了一个Swift分享,内容是官方文档的最后五章,包括扩展、协议、泛型、访问控制以及高级运算符,主要内容是扩展和协议,后三章一笔带过~

自己也是正在学习Swift,也算是记录下自己的学习经历。

1. Extensions 扩展

扩展 就是为一个已有的类、结构体、枚举类型或者协议类型添加新功能,与OC中的Category类似。

可以扩展的功能:

  • 为已有类型添加新的实例方法和类型方法
class Person {
	
	var name = "Jack"
	
	func work() {
	 
	}
}

extension Person {

	func age() {		// ✅可以添加新方法
	
	}

	func work() {		// ❌不可以重写方法
	
	}
}
  • 为已有类型添加新的便利构造器
class Size {
    var width: CGFloat = 0.0
    var height: CGFloat = 0.0
    
    init(width: CGFloat, height: CGFloat) {
        self.width = width
        self.height = height
    }
}

extension Size {
    convenience init(point: CGSize) {
        
        self.init(width: point.width, height: point.height)
    }
}
  • 为已有类型添加新下标
// 为Int扩展下标
extension Int {
    subscript(_ digtalNumber: Int) -> Int {
        
        return digtalNumber
    }
}

29476[3]		// 这里为 3
  • 为已有类型扩展计算型属性
extension Double {
    
    var m: Double { return self }
    var cm: Double { return self * 100.0 }
    var mm: Double { return self * 1000.0 }
    
    var invalid: Double		// ❌不可以添加存储属性
}
  • 为已有类、结构体、枚举添加新的嵌套类型
extension Int {
    enum Kind {
      //正数 负数 0
      case negative, zero, positive
    }
    var kind:Kind {
      switch self {
          case 0:
              return .zero
          case let x where x > 0:
              return .positive
          default:
              return .negative
      }
    }
}

let num1 = 3
if num1.kind == .positive {
    print("\(num1) am positive")
}
  • 通过扩展遵循协议
protocol RandomGenerator {
    
    func randomBool() -> Bool
}

extension SomeClass: RandomGenerator {

    func randomBool() -> Bool {
        return true
    }
}
  • 为已有协议添加默认实现和新的方法实现
protocol RandomGenerator {
    
    func randomBool() -> Bool
}

extension RandomGenerator {

	func randomBool() -> Bool {
		return true
	}
	
	func randomInt() -> Int {
		return 0
	}
}

2.Protocols 协议

协议定义一个规则去实现特定功能.类 结构体 枚举都可以遵守这个协议,并为这个协议的规则提供具体实现。

Apple在WWDC2015上提出一种编程范式POP(Protocol Oriented Programming),面向协议编程。在Swift标准库中有50多种复杂不一的协议,基本上串起了整个Swift,感觉还是要项目中多实践,不然还是会沦落为语法糖,而不是一种真正的面向协议编程的思想。

在WWDC2015的Session 408可以找到POP的回放。

遵循协议

声明一个协议

protocol SomeProtocol {

}
  • 自定义类、结构体遵循协议
struct SomeStruct: SomeProtocol, AnotherProtocol {

}

class SomeClass: SomeProtocol, AnotherProtocol {

}
  • 通过扩展遵循协议
extension SomeClass: SomeProtocol {

}

协议要求

方法属性

协议中的属性可以是实例属性也可以是类型属性,协议中的属性只能指定名称和类型以及可读可写。方法要求也和属性类似,可以加 mutating 关键字。

protocol SomeProtocl2 {
    var mustBeSettable: Int { get set }             // 可读可写
    var onlyRead: Int { get }                       // 只读
    static var someTypeProperty: Int { get set }    // 类属性前面加static

    func method() -> Void    
    static func classMethod() -> Void
    mutating func toggle() -> Void
}

如果有遵循协议的话,协议中的属性就必须要实现,所以如果不想实现就要用到可选型协议。

可选协议

在Swift3中,想实现协议中的方法可选,有两种方式:

  • 协议前加 @objc 关键字,方法和属性前也要加@objc关键字 使用到可选方法或者属性时,它们的类型会自动变为可选类型
@objc protocol CounterDataSource {
    @objc optional var fixAdd: Int { get }
    @objc optional func addForCount(count: Int) -> Int
}

但是这种方法有几个缺点:

  1. 只能被OC类或者带@objc关键字的类遵循,结构体和枚举都不能遵循
  2. 用到 @objc 关键字就免不了要与OC兼容,所以不推荐这种写法
  • 协议和方法都不加关键字,使用 Extension 扩展协议方法的默认实现。为了避免过多不必要的Objective-C兼容,一般推荐这种方法。
protocol CounterDataSource {
    
    var fixAdd: Int { get }				// optional
    
    func addForCount(count: Int) -> Int	// optional
    
    func degreeCount(count: Int) -> Int	// required
}

extension CounterDataSource {
    
    // 想要 fixAdd 和 addForCount(count: Int) 成为 optional 就对其进行扩展
    var fixAdd: Int {
    	// 这里对属性的getter方法进行实现
    }
    
    func addForCount(count: Int) -> Int {
		// 这里对方法进行实现
    }
}

class SomeClass: CounterDataSource {
    
    // 这里只需要实现 degreeCount(count: Int) 这一个 required 方法即可
    func degreeCount(count: Int) -> Int {
    	// 这里对方法进行实现
    }
}
构造器要求

构造方法需要遵循协议的构造器必须在方法前加上 required 关键字,来确保所有子类都要实现此构造器,如果是final修饰的类则不需要。

class SomeClass: SomeProtocol {
    required init(someParameter: Int) { 
    
    }
}

使用协议实现委托代理

委托模式可以用来响应特定的动作,或者接收外部数据源提供的数据,而无需关心外部数据源的类型。

使用协议同样可以完成委托代理,和OC食用方法类似

protocol SliderDelegate {
    func didDragSlider()
}

class Slider {
    
    var delegate: SliderDelegate?
    
    func drag() -> Void {
        // ....
        
        delegate?.didDragSlider()       
    }
}

class ViewController: UIViewController, SliderDelegate {
    
    override func viewDidLoad() {
    	let slider = Slider()
		slider.delegate = self
		slider.drag()
    }
    
    func didDragSlider() {
        print("didDragSlider ======= ")
    }   
}

我们在OC中会在声明中将delegate定义为weak,这样在delegate实际对象被释放时会被重置为nil,避免访问已被回收内存导致的crash。在Swift中想对delegate使用weak需要把protocol限制在类中,一种做法是将protocol声明为Objective-C的,在协议前加@objc关键字,因为OC的协议只有类能实现,还有一种方法就是下面说的类类型专属协议。

类类型专属协议

通过添加 class 关键字来限制协议只能被类类型遵循,而结构体或枚举不能遵循该协议。

protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {

}

回头看上面的委托代理,想使用weak修饰delegate,只要把协议设置成类类型专属协议就可以了。

protocol SliderDelegate: class {
    func didDragSlider()
}

class Slider {
    
    weak var delegate: SliderDelegate?
    
    func drag() -> Void {
        // ....
        
        delegate?.didDragSlider()       
    }
}

协议作为类型使用

协议可以像其他普通类型一样使用:

  • 作为函数、方法或者构造器中的参数类型或返回值类型
  • 作为变量常量或属性的类型
  • 作数组或其他容器中元素的类型

协议的继承

协议能够继承一个或多个其他协议,可以在继承的基础上增加新的要求。

当一个类遵循一个协议时,不仅要满足该协议的所有要求,同时也要满足其父类协议的要求。

面向协议编程与 Cocoa 的邂逅 - OneV's Den

面向协议编程 - Realm.io

3. Generics 泛型

泛型可以避免为类似的功能书写重复的代码,泛型参数可以看成是真实类型的占位符,当泛型被使用到时才会被真实类型替换。Swift中的Array和Dictionary都是泛型集合,可以创建包含不同类型的数组。

为什么使用泛型

  • 类型安全:对于插入容器中的对象的类型都是已知的
  • 减少冗余代码:对多种数据类型进行相同操作,避免潜在的代码错误
  • 灵活的依赖库:作为第三方库可以暴露一些接口,避免使用这些接口的开发者(同事?)被强制使用某种固定的类型。

食用方法

函数、方法、属性、下标以及构造器都可以当做泛型进行处理,它们自身可以成为泛型或者存在与某个泛型类型的上下文中。

类型参数是定义在<>紧跟着方法名后,它可以作用于:

  • 作为属性的类型
  • 作为枚举体中的关联类型
  • 作为某个方法的返回值或参数
  • 作为构造器的参数类型
  • 作为另一个泛型的类型参数,例如Array
// 重复添加到数组中
func duplicate<T>(item: T, numberOfTimes n: Int) -> [T] {
        
        var buffer: [T] = []
        for _ in 0 ..< n {
            buffer.append(item)
        }
        return buffer
    }
    
let obj = duplicate(item: "2333", numberOfTimes: 2)

// 这里的obj是类型[String]

4. Access Control 访问控制

访问级别

Swift中有五种访问级别: openpublicinternalfileprivateprivate,另外private(set)修饰外部只读变量,相当于OC中的readonly

  • open : module以外可以访问、重写或者继承。
  • public: module以外可以访问、不能重写或者继承。
  • internal(默认):当前module可用,无需任何import
  • fileprivate: 当前文件可访问。 多用在声明extension中需要的成员变量或者方法
  • private:当前声明区域可见

在实际项目中,我们的常用访问控制是这样的

class CameraViewController: UIViewController {
    
    override func viewDidLoad() {
        
        startRecording()	// ✅使用fileprivate就可以在作用域外部访问到
        endRecording()		// ⛔这里编译报错,因为使用private只能在作用域内部访问到,这里无法访问到 endRecording()
    }
}

extension ViewController {
    
    fileprivate func startRecording() -> Void {
    	// ...
		endRecording()	// ✅这样可以正常访问
    }
    
    private func endRecording() -> Void { }
}

class WeReachability {
	// 一些共用的属性需要用Public修饰保证多个文件都可以访问到
	public enum WeReachabilityError: Error { }	
}

Swift的设计目标之一就是一门安全的语言,所以才会有这么多访问权限,对于Swift3新出的 open是为了弥补public语义上的不足。

继承是一件危险的事情。尤其对于一个framework或者module的设计者而言。在自身的module内,类或者属性对于作者而言是清晰的,能否被继承或者override都是可控的。但是对于使用它的人,作者有时会希望传达出这个类或者属性不应该被继承或者修改。这个对应的就是 final。

final的问题在于在标记之后,在任何地方都不能override。而对于lib的设计者而言,希望得到的是在module内可以被override,在被import到其他地方后其他用户使用的时候不能被override。

这就是 open产生的初衷。通过open和public标记区别一个元素在其他module中是只能被访问还是可以被override。

访问级别基本原则

  • 不可以在某个实体中定义访问更低级别的实体
  • 如果 private 级别的类型中定义了嵌套类型,那么该嵌套类型就自动拥有private访问级别
  • 子类的访问级别不能高于父类的访问级别。不过可以通过重写为继承来的类成员提供更高的访问级别。
public class A {
    private func someMethod() {
    
    }
}

internal class B: A {
    override internal func someMethod() {
    
    }
}

5. Advanced Operators 高级运算符

这里可以查看官方文档完整的高级运算符